; Tetris
; A Tetris game for the Jupiter Ace
; from the Jupiter Ace Archive Team - www.jupiter-ace.co.uk

; Compile using TASM assembler:
;   TASM -80 -b -l tetris.asm tetris.tap

; Run from Jupiter Ace with:
;    LOAD tetris tetris

#define        FILENAME        .TEXT   "tetris    "
; keep it exactly 10 chars long!       |----------|

#include       "ace.inc"

;=======================================================================
;                                 Constants
;=======================================================================

LEVEL_TIMER_0  .EQU    2500   ; Level Timer (loop count to level up)
DLY_INNER      .EQU    2000   ; Inner Delay Factor (keyboard scans)
DLY_OUTER      .EQU    54     ; Outer Delay Factor
DLY_LEVEL_STEP .EQU    5      ; Delay Decrease Factor by Level
PIT_WIDTH      .EQU    10     ; Width of playing field
PIT_DEPTH      .EQU    20     ; Height of playing field
SCREEN_LINE    .EQU    32     ; Screen Line Width
TOP_LINE       .EQU    SCREEN + SCREEN_LINE + 11       ; Tetris Pit Top Line
INIT_POS       .EQU    TOP_LINE - SCREEN_LINE + 3      ; Initial Piece Screen Address
NEXT_POS       .EQU    SCREEN + (3 * SCREEN_LINE) + 3  ; Next Piece Frame Screen Address
MAX_LEVEL      .EQU    10     ; Maximum Game Level

; -------------------- Keys --------------------------------------------
KEY_LEFT       .EQU    'j'
KEY_DROP       .EQU    'k'
KEY_RIGHT      .EQU    'l'
KEY_TURN       .EQU    'i'
KEY_PAUSE      .EQU    'p'
KEY_QUIT       .EQU    'q'

; --------------------- Characters -------------------------------------
CHR_BRICK      .EQU    58
CHR_TILE       .EQU    59
CHR_TILE2      .EQU    60
CHR_BLOCK      .EQU    160
CHR_TOP_LEFT   .EQU    148
CHR_TOP_RIGHT  .EQU    23
CHR_BOT_LEFT   .EQU    145
CHR_BOT_RIGHT  .EQU    146
CHR_TOP        .EQU    19
CHR_BOTTOM     .EQU    147
CHR_LEFT       .EQU    149
CHR_RIGHT      .EQU    21

;=======================================================================
;                                  Variables
;=======================================================================
; Using Floating Point workspace (max 19 bytes) for variables
Score          .EQU    FpWs           ; 2 bytes  Score
Lines          .EQU    FpWs +  2      ; 2 bytes  Lines removed
Level          .EQU    FpWs +  4      ; 2 bytes  Current Level
Rotation       .EQU    FpWs +  6      ; 1 byte   Piece Rotation (bits 2-3)
Piece          .EQU    FpWs +  7      ; 1 byte   Current piece  (bits 4-6)
Next           .EQU    FpWs +  8      ; 1 byte   Next piece     (bits 4-6)
DelayFactor    .EQU    FpWs +  9      ; 1 byte   Delay Factor
DelayCount     .EQU    FpWs + 10      ; 1 byte   Delay Count
LastKey        .EQU    FpWs + 11      ; 1 byte   Holds last Key pressed
Temp           .EQU    FpWs + 12      ; 1 byte   General usage
LevelTimer     .EQU    FpWs + 13      ; 2 bytes  Level Timer

;=======================================================================
;                              Tape File Header
;=======================================================================
               .ORG    DICT_START - 30                 ; Make room for Tape file header

               .WORD   26                              ; TAP 1st chunck size
HEADER_BLK:    .BYTE   00                              ; File Type
               FILENAME                                ; Filename (10 bytes)
               .WORD   DATA_BLK_END - DATA_BLK         ; File Lenght
               .WORD   DICT_START                      ; Start Address
               .WORD   TETRIS_LNK                      ; link to newest word
               .WORD   3C4Ch                           ; CURRENT
               .WORD   3C4Ch                           ; CONTEXT
               .WORD   3C4Fh                           ; VOCLNK
               .WORD   DATA_BLK_END                    ; STKBOT
               .CHK    HEADER_BLK                      ; Header Block CheckSum
               .WORD   DATA_BLK_END-DATA_BLK + 1       ; TAP 2nd chunck size
DATA_BLK:                                              ; Data Block Start

;=======================================================================
;                          TETRIS word header
;=======================================================================
TETRIS:        .BYTE   "TETRI",'S' | BIT_INVERSE      ; Word Name
               .WORD   TETRIS_END - $                 ; Word Lenght Field
               .WORD   3C49h                          ; Link Field
TETRIS_LNK:    .BYTE   $ - TETRIS - 4                 ; Name Lenght Field
               .WORD   gameStart                      ; Code Field Address

;=======================================================================
;                            Tetris code
;=======================================================================
; 'Piece' bits:  76543210
;                0|||||^^---> Tile index   (4 tiles/piece)
;                 |||^^-----> Rotation     (4 possible orientations)
;                 ^^^-------> Piece Number (0 to 7)

;-----------------------------------------------------------------------;
; Main Tetris Code                                                      ;
;-----------------------------------------------------------------------;

; Set Programmable Characters
gameStart:
       LD      HL,TBL_CHARACTERS       ; Character pattern table
       LD      DE,CHAR_SET + (8 * 48)  ; Starting with character '0' (ASCII 48)
       LD      BC,13 * 8               ; 13 Characters: Numbers 0-9, brick & tiles
       LDIR

; Initialize Variables
initialize:
       LD      HL,FpWs                 ; Fill all 19 bytes of FpWs with 0
       LD      DE,19
       LD      B,0
       CALL    fill
       CALL    levelUp                 ; Set Initial Level to 1

; Draw Game Screen -----------------------------------------------------

; Fill Screen Background
       LD      HL,SCREEN               ; From screen top left
       LD      DE,SCREEN_LINE * 23     ; All Screen lines
       LD      B,CHR_BRICK             ; Fill with Brick Char
       CALL    fill

; Draw Pit
       LD      HL,STR_TETRIS           ; Pit Top
       CALL    printInverseString

       LD      DE,SCREEN_LINE - 12     ; Move pointer to next line
       ADD     HL,DE
       LD      C,PIT_DEPTH
nextPitLine:
       LD      (HL),' ' | BIT_INVERSE  ; Left Border
       INC     HL
       CALL    clearLine               ; Pit Line
       LD      (HL),' ' | BIT_INVERSE  ; Right Border
       LD      DE, SCREEN_LINE - 11    ; Move pointer to next line
       ADD     HL,DE
       DEC     C                       ; Draw all lines
       JR      NZ,nextPitLine

       LD      HL,STR_PIT_BOTTOM       ; Pit Bottom
       CALL    printInverseString

; Draw Frames with labels
       LD      HL,STR_NEXT             ; "Next" Frame String
       LD      C,4                     ; Frame Height
       CALL    drawFrame

       LD      HL,STR_SCORE            ; "Score" Frame String
       LD      C,1                     ; Frame Height
       CALL    drawFrame

       LD      HL,STR_LEVEL            ; "Level" frame string
       LD      C,1                     ; Frame Height
       CALL    drawFrame

       LD      HL,STR_LINES            ; "Lines" frame string
       LD      C,1                     ; Frame Height
       CALL    drawFrame

; Print other Labels
       LD      HL,STR_JAAT             ; Jupiter Ace Archive Team
       CALL    printInverseString

       LD      HL,STR_YEAR             ; Year
       CALL    printInverseString

       LD      HL,STR_KEY_LEFT         ; Instruction
       CALL    printInverseString

       LD      HL,STR_KEY_RIGHT        ; Instruction
       CALL    printInverseString

       LD      HL,STR_KEY_TURN         ; Instruction
       CALL    printInverseString

       LD      HL,STR_KEY_DROP         ; Instruction
       CALL    printInverseString

       LD      HL,STR_KEY_PAUSE        ; Instruction
       CALL    printInverseString

       LD      HL,STR_KEY_QUIT         ; Instruction
       CALL    printInverseString

; Generate and show a random Next Piece
       CALL    nextRandom

; Main Game Loop  -----------------------------------------------------

newPiece:
       LD      A,(Next)                ; Current Piece = Next Piece
       LD      (Piece),A

       CALL    unDrawNextPiece         ; Undraw "Next Piece"

       LD      HL,INIT_POS             ; Set Initial Position
       LD      (ScrPos),HL

       XOR     A                       ; Reset Rotation
       LD      (Rotation),A

       CALL    nextRandom              ; Generate a new "Next Piece" and show it

; Check if piece can be draw. If not, Game Over
       LD      A,(Piece)               ; Get Piece
       LD      HL,Rotation             ; Apply rotation
       OR      (HL)
       LD      DE,(ScrPos)             ; Get Screen Address
       CALL    checkDraw               ; Piece can be draw?
       JR      NZ,gameOver             ; If not, Game Over

; Check Level Timer. (Is it time to level up?)
       LD      HL,LevelTimer + 1       ; Check Level Timer high byte
       BIT     7,(HL)                  ; Is Timer < 0 ?
       CALL    NZ,levelUp              ; Yes, advance Level

; Calculate Delay Factor based on current Game Level
       LD      A,(Level)               ; Calculate Outer Delay factor..
       LD      B,A                     ; ..based on current Level
       LD      A,DLY_OUTER
_delayDecrease:                        ; A = DLY_OUTER - Level*DLY_LEVEL_STEP
       SUB     DLY_LEVEL_STEP
       DJNZ    _delayDecrease
       LD      (DelayFactor),A

       CALL    printScore              ; Show score

pieceLoop:
       CALL    drawCurrentPiece        ; Draw Piece
       LD      A,(DelayFactor)         ; Initialize Delay Countdown
       LD      (DelayCount),A

delayLoop:
       LD      HL,(LevelTimer)         ; Decrement Level Timer
       DEC     HL
       LD      (LevelTimer),HL

; Inner delay loop
       LD      BC,DLY_INNER            ; Initialize Inner delay loop
shortDelay:
       DEC     BC
       LD      A,B
       OR      C
       JR      NZ,shortDelay

; Check for user input
       CALL    scanKeyboard            ; Get Keypress
       LD      HL,LastKey              ; Check if it was just pressed
       CP      (HL)
       LD      (LastKey),A             ; Save it
       JR      Z,noUserInput

       CP      KEY_QUIT                ; Check for Quit Game request
       JR      Z,gameOver

       CALL    userAction              ; Execute User requested action

noUserInput:
       LD      HL,DelayCount           ; Decrement Delay Count
       DEC     (HL)
       JR      NZ,delayLoop            ; Loop if not done

       CALL    unDrawCurrentPiece      ; Undraw Piece
; Check if piece can be draw just below current position
       LD      A,(Piece)               ; Get Piece
       LD      HL,Rotation             ; Apply rotation
       OR      (HL)
       LD      HL,(ScrPos)             ; Point to one line below
       LD      BC,SCREEN_LINE
       ADD     HL,BC
       EX      DE,HL
       CALL    checkDraw               ; Can be draw?
       JR      NZ,stopPiece            ; No, Stop this Piece

; Move Piece One Line Down
       LD      HL,(ScrPos)             ; Get current position
       LD      BC,SCREEN_LINE          ; Add one line
       ADD     HL,BC
       LD      (ScrPos),HL             ; Update variable
       CALL    drawCurrentPiece        ; Draw Piece
       JP      pieceLoop

stopPiece:
       CALL    drawCurrentPiece
       CALL    collectLines            ; Collect Filled Lines, updating score
       LD      A,KEY_DROP              ; Stop dropping command
       LD     (LastKey),A
       JP      newPiece

gameOver:
       LD      HL,STR_GAME             ; Print 'Game Over'
       CALL    printInverseString
       LD      HL,STR_OVER             ; Print 'Game Over'
       CALL    printInverseString
       CALL    playGameOverBuzz
       CALL    waitKeyPress            ; Wait for any key press to proceed
       JP      initialize              ; Play again

;------------- Main code end --------------

;-----------------------------------------------------------------------;
; Fill memory area with a byte                                          ;
;-----------------------------------------------------------------------;
; Input:   HL = Start address         | Output:  HL = Next Address
;          DE = Lenght                |          DE = 0
;           B = Byte to use           |           A = 0
; Affects: A D E H L

fill:
       LD      (HL),B
       INC     HL
       DEC     DE
       LD      A,D
       OR      E
       JR      NZ,fill
       RET

;-----------------------------------------------------------------------;
; Fill Pit Line with a character                                        ;
;-----------------------------------------------------------------------;
; Input:  HL = Line Address           | Output:  HL = Next Screen Position
;          A = Character to use
; Affects: B

fillLine:
       LD      B,PIT_WIDTH
_fillLineLoop:
       LD      (HL),A
       INC     HL
       DJNZ    _fillLineLoop
       RET

;-----------------------------------------------------------------------;
; Clear Line                                                            ;
;-----------------------------------------------------------------------;
; Input:  HL = Line Address            | Output: HL = Next screen position
; Affects: A, B

clearLine:
       LD      A,' '
       JP      fillLine

;-----------------------------------------------------------------------;
; Highlight a  Line                                                     ;
;-----------------------------------------------------------------------;
; Input:  HL = Line Address            | Output: HL = Next screen position
; Affects: A, B

highlightLine:
       LD      A,CHR_TILE2
       JP      fillLine

;-----------------------------------------------------------------------;
; Print String in Inverse Video                                         ;
;-----------------------------------------------------------------------;
;  Input: HL = String (scren address prefix, zero terminated)
; Output: HL = next screen position

printInverseString:
       LD      E,(HL)          ; Get Screen address
       INC     HL
       LD      D,(HL)
       INC     HL
       EX      DE,HL           ; DE = Zero terminated string address

_nextPrintInv:                 ; HL = Screen address
       LD      A,(DE)          ; Get char from string
       OR      A               ; No more chars? (0)
       RET     Z               ; Return

       OR      BIT_INVERSE     ; Make it inverse video
       LD      (HL),A          ; Place character
       INC     HL              ; Advance string char pointer
       INC     DE              ; Advance screen position
       JR      _nextPrintInv   ; Print next character

;-----------------------------------------------------------------------;
; Draw a Frame of width 7                                               ;
;-----------------------------------------------------------------------;
; Input: HL = Title String (screen address prefix, zero terminated)
;         C = Frame Height

drawFrame:
       PUSH    HL                      ; Save string pointer
       LD      E,(HL)                  ; Get screen address to DE
       INC     HL
       LD      D,(HL)
       POP     HL                      ; Recover string pointer
       PUSH    DE                      ; Save screen address
       CALL    printInverseString
       POP     DE                      ; Recover screen address

; Calculate Frame Width
       OR      A                       ; Clear Carry Before Subtraction
       SBC     HL,DE                   ; Current Address - Initiaql Address
       LD      A,L                     ; A = Frame Width
       DEC     A                       ; Adjust for internal width (excluding borders)
       DEC     A
       EX      DE,HL                   ; HL = Screen Address
       LD      DE,SCREEN_LINE          ; Advance to Next Line
       ADD     HL,DE
       PUSH    HL                      ; Save Current Address
       LD      (HL),CHR_TOP_LEFT       ; Draw Top Left Corner
       INC     HL
       LD      B,A                     ; B = Frame Width

_frameTopLine:                         ; Draw Frame Top Line
        LD     (HL),CHR_TOP
        INC    HL
        DJNZ   _frameTopLine

        LD     (HL),CHR_TOP_RIGHT      ; Draw Top Right Corner
        POP    HL                      ; Recover Line Start Address
        ADD    HL,DE                   ; Advance to Next Line

_nextFrameLine:                        ; Draw Frame Lines
       PUSH    HL
       LD      (HL),CHR_LEFT
       INC     HL
       LD      B,A                     ; Frame Width

_frameLine:
       LD      (HL),' '
       INC     HL
       DJNZ    _frameLine

       LD      (HL),CHR_RIGHT
       POP     HL
       ADD     HL,DE                   ; Next Line
       DEC     C
       JR      NZ,_nextFrameLine

       LD      (HL),CHR_BOT_LEFT       ; Draw Frame Bottom Line
       INC     HL
       LD      B,A                     ; Frame Width

_frameBotLine:
       LD      (HL),CHR_BOTTOM
       INC     HL
       DJNZ    _frameBotLine

       LD      (HL),CHR_BOT_RIGHT
       RET

;-----------------------------------------------------------------------;
; Print Integer Value (2 bytes) with left zero suppression                    ;
;-----------------------------------------------------------------------;
; Input:   HL = Integer number to print
;          DE = Screen Location
; Affects: A B C D E H L and the variable 'Temp'
; (code adapted from web page "Z80 bits" by Milos Bazelides)

printValue:
       LD      A,' '           ; Prepare for left zeroes suppression
       LD      (Temp),A
       LD      BC,-10000       ; Print 10000th
       CALL    _printTh
       LD      BC,-1000        ; Print 1000th
       CALL    _printTh
       LD      BC,-100         ; Print 100th
       CALL    _printTh
       LD      C,-10           ; Print 10th
       CALL    _printTh
       LD      C,-1            ; Print units
       LD      A,'0'           ; rightmost algarism not to be suppressed
       LD      (Temp),A	
_printTh:
       LD      A,'0' - 1
_printThLoop:
       INC     A
       ADD     HL,BC
       JR      C,_printThLoop

       SBC     HL,BC
       CP      '0'             ; algarism is 0, suppress it
       JR      Z,_zeroSuppress
    
       LD      (DE),A          ; Print non-zero algarism to the screen
       INC     DE              ; Advance screen pointer
       LD      A,'0'           ; Stop zero suppression on next algarism
       LD      (Temp),A
       RET

_zeroSuppress:
       LD      A,(Temp)        ; Print SPACE or 0 to the screen
       LD      (DE),A
       INC     DE              ; Advance screen pointer
       RET

;-----------------------------------------------------------------------;
; Print Score                                                           ;
;-----------------------------------------------------------------------;

printScore:
       LD      HL,(Score)              ; Print Score
       LD      DE,SCREEN+( 3*SCREEN_LINE)+24
       CALL    printValue

       LD      HL,(Level)              ; Print Level
       LD      DE,SCREEN+(9*SCREEN_LINE)+24
       CALL    printValue

       LD      HL,(Lines)              ; Print Lines
       LD      DE,SCREEN+(15*SCREEN_LINE)+24
       CALL    printValue
       RET

;-----------------------------------------------------------------------;
; Increment Level                                                       ;
;-----------------------------------------------------------------------;
; Affects: A H L
levelUp:
       LD      HL,LEVEL_TIMER_0        ; Reset Level Timer
       LD      (LevelTimer),HL
       LD      HL,Level                ; Level reached ?
       LD      A,MAX_LEVEL
       XOR     (HL)
       RET     Z

       INC     (HL)                    ; Increment Level
       CALL    playLevelUpBip
       RET

;-----------------------------------------------------------------------;
; Get Current Piece with Rotation applyed                               ;
;-----------------------------------------------------------------------;
; Output:   A = Piece with Rotation
;          DE = Screen Address
; Affects: A D E H L

getPiece:
       LD      A,(Piece)       ; Get piece
       LD      HL,Rotation     ; and apply rotation
       OR      (HL)
       LD      DE,(ScrPos)     ; Get Screen Address
       RET

;-----------------------------------------------------------------------;
; Draw Piece                                                            ;
;-----------------------------------------------------------------------;
; Input:  A = Piece with rotation already applied
;         C = Character to use when drawing
;        DE = Screen Address

drawPiece:
       LD      HL,TBL_PIECES   ; Point to Piece Pattern
       ADD     A,L             ; Table location = HL + A
       JR      NC,_draw1
       INC     H

_draw1:
       LD      L,A
       LD      B,4             ; Four tiles to draw

_drawNextTile:
       LD      A,(HL)          ; Get tile offset
       ADD     A,E             ; Apply offset to screen address
       JR      NC,_placeBrick
       INC     D

_placeBrick:
       LD      E,A
       LD      A,C
       LD      (DE),A          ; Place a Brick into screen
       INC     HL              ; Point to Next tile offset
       DJNZ    _drawNextTile
       RET

;-----------------------------------------------------------------------;
; Check if piece can be draw                                            ;
;-----------------------------------------------------------------------;
; Input:  A = Piece with rotation applied
;        DE = Screen Address
; Output: Zero flag set if no other tile on the way

checkDraw:
       LD      HL,TBL_PIECES   ; Piece Pattern Table Address
       LD      C,A
       LD      B,0             ; Expand BC to 16 bits before adding
       ADD     HL,BC           ; Find correct position in table (HL)
       LD      B,4             ; Every piece has 4 tiles

_checkNextTile:
       LD      A,(HL)          ; Get tile offset
       ADD     A,E             ; Apply offset to screen address
       JR      NC,_checkTile
       INC     D

_checkTile:
       LD      E,A
       LD      A,(DE)          ; Read a Tile from screen
       CP      ' '             ; Check if not a Space
       RET     NZ

       INC     HL              ; Point to Next tile offset
       DJNZ    _checkNextTile
       XOR     A               ; Clear Zero Flag
       RET

;-----------------------------------------------------------------------;
; Draw Current Piece                                                    ;
;-----------------------------------------------------------------------;

drawCurrentPiece:
       LD      C,CHR_TILE
       CALL    getPiece
       JP      drawPiece

;-----------------------------------------------------------------------;
; Undraw Current Piece                                                  ;
;-----------------------------------------------------------------------;

unDrawCurrentPiece:
       LD      C,' '
       CALL    getPiece
       JP      drawPiece

;-----------------------------------------------------------------------;
; Undraw Next Piece                                                     ;
;-----------------------------------------------------------------------;

unDrawNextPiece:
       LD      C,' '
       LD      A,(Next)
       LD      DE,NEXT_POS
       JP      drawPiece

;-----------------------------------------------------------------------;
; Generate a random Next Piece and show it                              ;
;-----------------------------------------------------------------------;

nextRandom:
       LD      A,(Frames)      ; Generate a random Next Piece
       RLA                     ; Move to bits 4-6
       RLA
       RLA
       RLA
       AND     01110000b
       LD      (Next),A        ; Next = Random
       LD      C,CHR_TILE      ; Draw Next Piece
       LD      DE,NEXT_POS
       JP      drawPiece
	   
;-----------------------------------------------------------------------;
; Remove line moving above lines down                                   ;
;-----------------------------------------------------------------------;
; Input: HL = Line Address

removeLine:
       LD      D,H             ; DE = Destination
       LD      E,L
       LD      BC,-SCREEN_LINE ; HL = Source (above line)
       ADD     HL,BC
       PUSH    HL              ; Save above line address
       LD      BC,PIT_WIDTH
       LDIR                    ; Copy above line over current line

; Check if top line reached
       LD      BC,TOP_LINE + PIT_WIDTH
       OR      A               ; Clear Carry before subtraction
       SBC     HL,BC           ; Set Z flag if top line reached
       POP     HL              ; Get above line address
       JR      NZ,removeLine

; Clear Top Line
       CALL    clearLine
       RET

;-----------------------------------------------------------------------;
; Collect Filled lines                                                  ;
;-----------------------------------------------------------------------;

collectLines:
       LD      B,PIT_DEPTH             ; Line Count
       LD      HL,TOP_LINE             ; Check from top to bottom

_collectNext:
       PUSH    BC                      ; Save Line Count
       PUSH    HL                      ; Save Line Address
       LD      B,PIT_WIDTH             ; Search along a line width..
       LD      A,' '                   ; ..for a space char

_searchSpace:
       CP      (HL)                    ; Zero Flag set if a space is found
       JR      Z, _lineNotFilled       ; Line is not filled, skip it.
       INC     HL                      ; Next screen column
       DJNZ    _searchSpace

; Line is filled. Remove it.
       LD      DE,-PIT_WIDTH
       ADD     HL,DE
       PUSH    HL
       CALL    highlightLine           ; Flash Line before removing
       CALL    playLineSound           ; Play a sound
       POP     HL
       CALL    removeLine              ; Remove line
       LD      DE,(Lines)              ; Count removed lines
       INC     DE
       LD      (Lines),DE
       CALL    scoreLine               ; Score removed lines

_lineNotFilled:
       POP     HL
       POP     BC
       LD      DE, SCREEN_LINE
       ADD     HL,DE
       DJNZ    _collectNext            ; Go check next line
       RET

;-----------------------------------------------------------------------;
; Sound for collecting lines                                            ;
;-----------------------------------------------------------------------;
; Affect: A, B, C

playLineSound:
       DI                              ; Avoid interrupt during sound play
       LD      C,96                    ; Number of cycles to play

_lineSoundLoop:
       LD      B,C                     ; Set 1st Half Cycle delay factor
       DJNZ    $                       ; 1st Half cycle delay
       pullSpeaker                     ; Pull in the speaker diaphragm
       LD      B,C                     ; Set 2nd Half Cycle delay factor
       DJNZ    $                       ; 2nd Half cycle delay
       pushSpeaker                     ; Push out the speaker diaphragm
       DEC     C                       ; Count number of cycles
       JR      NZ,_lineSoundLoop       ; loop back if not done
       EI                              ; Restore interrupt
       RET
 
;-----------------------------------------------------------------------;
; Play a Tone                                                           ;
;-----------------------------------------------------------------------;
; Input: E = Tone
;        D = Duration (cycles)

playTone:
       DI                      ; Avoid interrupt during sound play

_toneLoop:
       LD      B,E             ; Set 1st Half Cycle delay factor
       DJNZ    $               ; 1st Half cycle delay
       pullSpeaker             ; Pull in the speaker diaphragm
       LD      B,E             ; Set 2nd Half Cycle delay factor
       DJNZ    $               ; 2nd Half cycle delay
       pushSpeaker             ; Push out the speaker diaphragm
       DEC     D               ; Count number of cycles
       JR      NZ,_toneLoop    ; loop back if not done
       EI                      ; Restore interrupt
       RET

;-----------------------------------------------------------------------;
; Play Level Up Bip                                                   ;
;-----------------------------------------------------------------------;

playLevelUpBip:
       LD      D,32            ; Duration
       LD      E,32            ; Tone
       JP      playTone
       
;-----------------------------------------------------------------------;
; Play Game Over Buzz                                                   ;
;-----------------------------------------------------------------------;
playGameOverBuzz:
       LD      D,200           ; Duration
       LD      E,255           ; Tone
       JP      playTone

;-----------------------------------------------------------------------;
; Scoring Collected Lines                                               ;
;-----------------------------------------------------------------------;

scoreLine:
       LD      DE,(Score)
       LD      HL,(Level)
       ADD     HL,DE
       LD      (Score),HL
       RET

;-----------------------------------------------------------------------;
; Wait for a Keypress                                                   ;
;-----------------------------------------------------------------------;

waitKeyPress:
       CALL    scanKeyboard    ; Wait for no key pressed
       JR      NZ,waitKeyPress
_waitKey:
       CALL    scanKeyboard    ; Wait for a key press
       JR      Z,_waitKey
       RET

;-----------------------------------------------------------------------;
; Check User Input                                                      ;
;-----------------------------------------------------------------------;
; Input: A = Key pressed

userAction:
       OR      A
       RET     Z                       ; No keypressed do nothing

       CALL    unDrawCurrentPiece
       LD      A,(LastKey)
       CP      KEY_TURN                ; Rotate Piece
       JR      Z,_rotate
       CP      KEY_LEFT                ; Move Piece Left
       JR      Z,_moveLeft
       CP      KEY_RIGHT               ; Move Piece Right
       JR      Z,_moveRight
       CP      KEY_DROP                ; Drop Piece
       JR      Z,_dropPiece
       CP      KEY_PAUSE               ; Pause Game
       JR      Z,_pause

_userActionEnd:                        ; Other Key: do nothing
       JP      drawCurrentPiece

_rotate:
       LD      A,(Rotation)            ; Get current rotation
       ADD     A,00000100b             ; Rotate
       AND     00001100b               ; Mask Rotation bits (2 and 3)
       LD      (Temp),A                ; Save result to be used soon
       LD      HL,Piece                ; Get current piece
       OR      (HL)                    ; Apply rotation
       LD      DE,(ScrPos)             ; Check if piece can be draw
       CALL    checkDraw
       JR      NZ,_userActionEnd       ; If not, do not rotate
       LD      A,(Temp)                ; If ok, save new rotation
       LD      (Rotation),A
       JR      _userActionEnd

_moveLeft:
       CALL    getPiece
       DEC     DE                      ; Check if moving left is possible
       CALL    checkDraw
       JR      NZ,_userActionEnd
       LD      DE,(ScrPos)             ; If possible move left
       DEC     DE
       LD      (ScrPos),DE
       JR      _userActionEnd

_moveRight:
       CALL    getPiece
       INC     DE                      ; Check if moving right is possible
       CALL    checkDraw
       JR      NZ,_userActionEnd
       LD      DE,(ScrPos)             ; If possible move right
       INC     DE
       LD      (ScrPos),DE
       JR      _userActionEnd

_dropPiece:
       LD      HL,1                    ; Speed up the drop loop
       LD      (DelayCount),HL
       XOR     A                       ; Clear LastKey allowing key repeat effect
       LD      (LastKey),A
       JR      _userActionEnd

_pause:
       CALL    drawCurrentPiece        ; Show Current Piece
       LD      HL,STR_PAUSED           ; Show Pause Message
       CALL    printInverseString
       CALL    waitKeyPress            ; Wait for user input
       LD      HL,STR_PIT_BOTTOM       ; Remove Pause Message
       JP      printInverseString

;=======================================================================
;                                 Constants
;=======================================================================

;-----------------------------------------------------------------------;
; Piece patterns at 4 possible rotations                                ;
;-----------------------------------------------------------------------;
; Each line represents a different piece rotation.
; There are four lines (rotations) per piece.
; The 4 values are to be added in sequence to a screen address.

TBL_PIECES:
;                                -------Rotation-------
;                                   0     1     2     3
       .BYTE  33, 01, 31, 01   ; ----  ----  ----  ----  0: Piece 'O'
       .BYTE  33, 01, 31, 01   ; -##-  -##-  -##-  -##-
       .BYTE  33, 01, 31, 01   ; -##-  -##-  -##-  -##-
       .BYTE  33, 01, 31, 01   ; ----  ----  ----  ----

       .BYTE  32, 01, 01, 01   ; ----  --#-  ----  --#-  1: Piece 'I'
       .BYTE  02, 32, 32, 32   ; ####  --#-  ####  --#-
       .BYTE  32, 01, 01, 01   ; ----  --#-  ----  --#-
       .BYTE  02, 32, 32, 32   ; ----  --#-  ----  --#-

       .BYTE  32, 01, 01, 31   ; ----  -#--  -#--  -#--  2: Piece 'T'
       .BYTE  01, 32, 01, 31   ; ###-  -##-  ###-  ##--
       .BYTE  01, 31, 01, 01   ; -#--  -#--  ----  -#--
       .BYTE  01, 31, 01, 32   ; ----  ----  ----  ----

       .BYTE  33, 01, 30, 01   ; ----  -#--  ----  -#--  3: Piece 'S'
       .BYTE  01, 32, 01, 32   ; -##-  -##-  -##-  -##-
       .BYTE  33, 01, 30, 01   ; ##--  --#-  ##--  --#-
       .BYTE  01, 32, 01, 32   ; ----  ----  ----  ----

       .BYTE  32, 01, 32, 01   ; ----  --#-  ----  --#-  4: Piece 'Z'
       .BYTE  02, 31, 01, 31   ; ##--  -##-  ##--  -##-
       .BYTE  32, 01, 32, 01   ; -##-  -#--  -##-  -#--
       .BYTE  02, 31, 01, 31   ; ----  ----  ----  ----

       .BYTE  32, 01, 01, 32   ; ----  -##-  #---  -#--  5: Piece 'J'
       .BYTE  01, 01, 31, 32   ; ###-  -#--  ###-  -#--
       .BYTE  00, 32, 01, 01   ; --#-  -#--  ----  ##--
       .BYTE  01, 32, 31, 01   ; ----  ----  ----  ----

       .BYTE  32, 01, 01, 30   ; ----  -#--  --#-  ##--  6: Piece 'L'
       .BYTE  01, 32, 32, 01   ; ###-  -#--  ###-  -#--
       .BYTE  02, 30, 01, 01   ; #---  -##-  ----  -#--
       .BYTE  00, 01, 32, 32   ; ----  ----  ----  ----

       .BYTE  32, 01, 01, 31   ; ----  -#--  -#--  -#--  2: Piece 'T' (again)
       .BYTE  01, 32, 01, 31   ; ###-  -##-  ###-  ##--
       .BYTE  01, 31, 01, 01   ; -#--  -#--  ----  -#--
       .BYTE  01, 31, 01, 32   ; ----  ----  ----  ----

;-----------------------------------------------------------------------;
; Strings - Format: Screen address followed by a zero terminated string ;
;-----------------------------------------------------------------------;

STR_TETRIS             .WORD   SCREEN + 10
                       .BYTE   " ACE TETRIS ", 0

STR_PIT_BOTTOM         .WORD   SCREEN + (21*SCREEN_LINE) + 10
                       .BYTE   "____________", 0

STR_PAUSED             .WORD   SCREEN + (21*SCREEN_LINE) + 11
                       .BYTE   "**PAUSED**", 0

STR_GAME               .WORD   SCREEN + (10*SCREEN_LINE) + 12
                       .BYTE   "  GAME  ", 0

STR_OVER               .WORD   SCREEN + (11*SCREEN_LINE) + 12
                       .BYTE   "  OVER  ", 0

STR_NEXT               .WORD   SCREEN + ( 1*SCREEN_LINE) +  2
                       .BYTE   " Next ",0

STR_SCORE              .WORD   SCREEN + ( 1*SCREEN_LINE) + 23
                       .BYTE   " Score ",0

STR_LEVEL              .WORD   SCREEN + ( 7*SCREEN_LINE) + 23
                       .BYTE   " Level ",0

STR_LINES              .WORD   SCREEN + (13*SCREEN_LINE) + 23
                       .BYTE   " Lines ",0

STR_KEY_PAUSE          .WORD   SCREEN + (19*SCREEN_LINE) + 23
                       .BYTE   " P Pause",0

STR_KEY_QUIT           .WORD   SCREEN + (20*SCREEN_LINE) + 23
                       .BYTE   " Q Quit ",0

STR_JAAT               .WORD   SCREEN + (11*SCREEN_LINE) +  2
                       .BYTE   " JAAT ",0

STR_YEAR               .WORD   SCREEN + (12*SCREEN_LINE) +  2
                       .BYTE   " 2009 ",0

STR_KEY_LEFT           .WORD   SCREEN + (17*SCREEN_LINE) +  1
                       .BYTE   " J Left ",0

STR_KEY_RIGHT          .WORD   SCREEN + (18*SCREEN_LINE) +  1
                       .BYTE   " L Right",0

STR_KEY_TURN           .WORD   SCREEN + (19*SCREEN_LINE) +  1
                       .BYTE   " I Turn ",0

STR_KEY_DROP           .WORD   SCREEN + (20*SCREEN_LINE) +  1
                       .BYTE   " K Drop ",0

;-----------------------------------------------------------------------;
; Programmable Characters                                               ;
;-----------------------------------------------------------------------;

TBL_CHARACTERS:
       .BYTE   01111110b       ; Zero
       .BYTE   01000010b
       .BYTE   01000010b
       .BYTE   01000010b
       .BYTE   01110010b
       .BYTE   01110010b
       .BYTE   01110010b
       .BYTE   01111110b

       .BYTE   00001000b       ; One
       .BYTE   00001000b
       .BYTE   00001000b
       .BYTE   00001000b
       .BYTE   00011000b
       .BYTE   00011000b
       .BYTE   00011000b
       .BYTE   00011000b

       .BYTE   01111110b       ; Two
       .BYTE   01100010b
       .BYTE   01100010b
       .BYTE   00000010b
       .BYTE   00111110b
       .BYTE   01100000b
       .BYTE   01100000b
       .BYTE   01111110b

       .BYTE   01111110b       ; Three
       .BYTE   01100110b
       .BYTE   00000110b
       .BYTE   00011100b
       .BYTE   00000110b
       .BYTE   00000110b
       .BYTE   01100110b
       .BYTE   01111110b

       .BYTE   00011100b       ; Four
       .BYTE   00100100b
       .BYTE   01000100b
       .BYTE   01000100b
       .BYTE   01111110b
       .BYTE   00001100b
       .BYTE   00001100b
       .BYTE   00001100b

       .BYTE   01111100b       ; Five
       .BYTE   01100000b
       .BYTE   01100000b
       .BYTE   01111110b
       .BYTE   00000010b
       .BYTE   00000010b
       .BYTE   01100010b
       .BYTE   01111110b

       .BYTE   01111110b       ; Six
       .BYTE   01000110b
       .BYTE   01000000b
       .BYTE   01111110b
       .BYTE   01100010b
       .BYTE   01100110b
       .BYTE   01100110b
       .BYTE   01111110b

       .BYTE   01111110b       ; Seven
       .BYTE   00000010b
       .BYTE   00000010b
       .BYTE   00000010b
       .BYTE   00000110b
       .BYTE   00000110b
       .BYTE   00000110b
       .BYTE   00000110b

       .BYTE   01111100b       ; Eight
       .BYTE   01000100b
       .BYTE   01000100b
       .BYTE   00111100b
       .BYTE   01100110b
       .BYTE   01100110b
       .BYTE   01100110b
       .BYTE   01111110b

       .BYTE   01111110b       ; Nine
       .BYTE   01000010b
       .BYTE   01000010b
       .BYTE   01000110b
       .BYTE   01111110b
       .BYTE   00000110b
       .BYTE   00000110b
       .BYTE   00000110b

       .BYTE   11101111b       ; Brick
       .BYTE   11101111b
       .BYTE   11101111b
       .BYTE   00000000b
       .BYTE   11111110b
       .BYTE   11111110b
       .BYTE   11111110b
       .BYTE   00000000b

       .BYTE   11111111b       ; Tile
       .BYTE   10101011b
       .BYTE   11010101b
       .BYTE   10101011b
       .BYTE   11010101b
       .BYTE   10101011b
       .BYTE   11010101b
       .BYTE   11111111b

       .BYTE   11111111b       ; Tile2
       .BYTE   10000001b
       .BYTE   10000001b
       .BYTE   10000001b
       .BYTE   10000001b
       .BYTE   10000001b
       .BYTE   10000001b
       .BYTE   11111111b

TETRIS_END:            ;================================================

DATA_BLK_END:
       .CHK    DATA_BLK              ; Data Block Checksum

;=======================================================================
;  Show code statistics when compiling
       .ECHO   "-----> Start Address: "
       .ECHO   DICT_START
       .ECHO   " , Lenght: "
       .ECHO   (DATA_BLK_END - DATA_BLK)
       .ECHO   " bytes\n"

       .END
